package com.liuya.db.orm.impl;

import java.sql.Connection;
import java.sql.PreparedStatement;
import java.sql.SQLException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.Iterator;

import com.liuya.common.exception.EntityExistException;
import com.liuya.common.Util;
import com.liuya.db.ColumnAdapter;
import com.liuya.db.DBLevelException;
import com.liuya.db.DBPower;
import com.liuya.db.orm.DefaultPageBatch;
import com.liuya.db.util.DBUtil;
import com.liuya.safe.exception.AuthException;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import com.liuya.db.orm.Column;
import com.liuya.db.orm.Table;
import com.liuya.db.orm.TableSaver;

public class TableSaverImpl implements TableSaver {
	private Table table;
	private String insertSql;
	private TableSelectorImpl selector;

	private static Log logger = LogFactory.getLog(TableSelectorImpl.class);

	public Table getTable() {
		return table;
	}

	public void setTable(Table table) {
		this.table = table;
		insertSql = DBUtil.insertSql(table.getName(), table.getColumnNames());

		selector = new TableSelectorImpl();
		selector.setTable(table);
	}

	public int[] batchSave(Object[] os) throws DBLevelException {
		Connection conn = null;
		try {
			conn = DBPower.getConnectionByTableId(table.getId());
			return batchSave(conn, os);
		} finally {
			DBUtil.close(conn);
		}
	}

	public Collection batchSave(Collection coll) throws DBLevelException {
		Connection conn = null;
		try {
			conn = DBPower.getConnectionByTableId(table.getId());
			return batchSave(conn, coll);
		} finally {
			DBUtil.close(conn);
		}
	}

	public int[] batchSave(Connection conn, Object[] os) throws DBLevelException {
		if (os == null)
			return null;

		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(insertSql);
			Column[] columns = table.getColumns();

			ArrayPageBatch apb = new ArrayPageBatch();
			apb.setPageSize(DBPower.getMaxBatchSize());
			apb.setColumns(columns);
			apb.setPreparedStatement(pstmt);
			apb.doBatch(os);

			return apb.getResult();
		} catch (SQLException e) {
			logger.error("", e);
			throw new DBLevelException(e);
		} catch (Exception e) {
			logger.error("", e);
			throw new AuthException(e);
		} finally {
			DBUtil.close(pstmt);
		}
	}

	public Collection batchSave(Connection conn, Collection coll) throws DBLevelException {
		if (coll == null)
			return null;

		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(insertSql);
			Column[] columns = table.getColumns();

			CollectionPageBatch cpb = new CollectionPageBatch();
			cpb.setPageSize(DBPower.getMaxBatchSize());
			cpb.setColumns(columns);
			cpb.setPreparedStatement(pstmt);
			cpb.doBatch(coll);

			return cpb.getResult();
		} catch (SQLException e) {
			logger.error("", e);
			throw new DBLevelException(e);
		} catch (Exception e) {
			logger.error("", e);
			throw new AuthException(e);
		} finally {
			DBUtil.close(pstmt);
		}
	}

	public void save(Object o) throws EntityExistException, DBLevelException {
		Connection conn = null;
		try {
			conn = DBPower.getConnectionByTableId(table.getId());
			save(conn, o);
		} finally {
			DBUtil.close(conn);
		}
	}

	public void save(Connection conn, Object o) throws EntityExistException, DBLevelException {
		PreparedStatement pstmt = null;
		try {
			pstmt = conn.prepareStatement(insertSql);
			Column[] columns = table.getColumns();
			for (int i = 0; i < columns.length; i++) {
				Column column = columns[i];
				ColumnAdapter columnAdapter = column.getAdapter();
				columnAdapter.setPreparedStatement(pstmt, i + 1, o);
			}

			pstmt.executeUpdate();
		} catch (SQLException e) {
			logger.error("", e);

			if (conn != null) {
				// violate pk or unique constraints?
				if (!Util.isEmpty(table.getIdColumns())) {
					boolean isExist = selector.isExistByIdColumns(conn, o);
					if (isExist) {
						throw new EntityExistException(o);
					}
				}

				if (!Util.isEmpty(table.getUniqueColumns())) {
					boolean isExist = selector.isExistByUniqueColumns(conn, o);
					if (isExist) {
						throw new EntityExistException(o);
					}
				}

				throw new DBLevelException(e);
			} else {
				throw new DBLevelException(e);
			}
		} finally {
			DBUtil.close(pstmt);
		}
	}

	class ArrayPageBatch extends DefaultPageBatch {
		private Column[] columns;
		private PreparedStatement pstmt;
		private int[] result;
		private int index = 0;

		public int[] getResult() {
			return result;
		}

		public void setColumns(Column[] columns) {
			this.columns = columns;
		}

		public void setPreparedStatement(PreparedStatement pstmt) {
			this.pstmt = pstmt;
		}

		public void doBatch(Object[] objs) throws Exception {
			result = new int[objs.length];
			super.doBatch(objs);
		}

		public void doInPage(Object[] objs) throws SQLException {
			for (int osIndex = 0; osIndex < objs.length; osIndex++) {
				for (int colIndex = 0; colIndex < columns.length; colIndex++) {
					Column column = columns[colIndex];
					ColumnAdapter columnAdapter = column.getAdapter();
					columnAdapter.setPreparedStatement(pstmt, colIndex + 1, objs[osIndex]);
				}
				pstmt.addBatch();
			}

			int[] pageResult = pstmt.executeBatch();
			// COPY insert page result into batch result
			for (int i = 0; i < pageResult.length; i++) {
				result[index] = pageResult[i];
				index++;
			}
		}
	}

	class CollectionPageBatch extends DefaultPageBatch {
		private Column[] columns;
		private PreparedStatement pstmt;
		private Collection result;

		public Collection getResult() {
			return result;
		}

		public void setColumns(Column[] columns) {
			this.columns = columns;
		}

		public void setPreparedStatement(PreparedStatement pstmt) {
			this.pstmt = pstmt;
		}

		public void doBatch(Collection coll) throws Exception {
			result = new ArrayList(coll.size());
			super.doBatch(coll);
		}

		public void doInPage(Collection coll) throws SQLException {
			for (Iterator iter = coll.iterator(); iter.hasNext();) {
				Object o = iter.next();
				for (int colIndex = 0; colIndex < columns.length; colIndex++) {
					Column column = columns[colIndex];
					ColumnAdapter columnAdapter = column.getAdapter();
					columnAdapter.setPreparedStatement(pstmt, colIndex + 1, o);
				}
				pstmt.addBatch();
			}

			int[] pageResult = pstmt.executeBatch();
			// COPY insert page result into batch result
			for (int i = 0; i < pageResult.length; i++) {
				result.add(new Integer(pageResult[i]));
			}
		}
	}
}